<!doctype html>
<html lang="zh-Hans" class="no-js">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>DevCast — WordPress风格播客</title>
  <meta name="description" content="一个 WordPress 风格的播客首页：文章卡片、两栏侧边、分页与搜索。可直接部署到 GitHub Pages。" />
  <meta name="theme-color" content="#2271b1" />
  <link rel="alternate" type="application/rss+xml" title="DevCast RSS" href="feed.xml" />
  <style>
    /* —— 基础调色，参考 WordPress 默认蓝 —— */
    :root{
      --bg:#f6f7f7;      /* 页面底色 */
      --paper:#ffffff;   /* 内容卡片 */
      --ink:#111827;     /* 正文 */
      --muted:#6b7280;   /* 次要 */
      --line:#e5e7eb;    /* 细线 */
      --primary:#2271b1; /* WP 蓝 */
      --primary-600:#135e96;
      --accent:#ffc107;  /* 强调 */
      --radius:14px;
      --shadow: 0 10px 30px rgba(0,0,0,.06);
    }
    @media (prefers-color-scheme: dark){
      :root{--bg:#0b0f12; --paper:#0f1419; --ink:#e6edf3; --muted:#9da8b3; --line:#1b2026; --shadow:0 10px 30px rgba(0,0,0,.35)}
    }
    html,body{height:100%}
    body{ margin:0; background:var(--bg); color:var(--ink); font:16px/1.7 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei UI", sans-serif; }
    img{max-width:100%; height:auto; display:block}
    a{color:var(--primary); text-decoration:none}
    a:hover{color:var(--primary-600)}
    code,kbd,pre,samp{font-family:ui-monospace, Menlo, Monaco, Consolas, "JetBrains Mono", monospace}

    .site{max-width:1200px; margin:0 auto; padding:0 20px}
    .site-header{position:sticky; top:0; z-index:10; background:linear-gradient(180deg, rgba(255,255,255,.85), rgba(255,255,255,.7)); backdrop-filter: blur(8px); border-bottom:1px solid var(--line)}
    @media (prefers-color-scheme: dark){ .site-header{ background:linear-gradient(180deg, rgba(15,20,25,.85), rgba(15,20,25,.65)); } }
    .header-inner{display:flex; align-items:center; gap:20px; padding:14px 0}
    .site-branding{display:flex; align-items:center; gap:12px}
    .site-title{font-weight:800; letter-spacing:.2px}
    .site-description{color:var(--muted); font-size:14px}
    .site-logo{width:36px; height:36px; border-radius:8px; display:grid; place-items:center; background:#fff; border:2px solid var(--primary); color:var(--primary); font-weight:900}

    .primary-menu{display:flex; gap:14px; margin-left:auto; align-items:center}
    .menu a{padding:8px 10px; border-radius:8px}
    .menu a[aria-current="page"], .menu a:hover{background:color-mix(in srgb, var(--primary) 12%, transparent);}

    .search-form{display:flex; align-items:center; gap:8px; background:var(--paper); border:1px solid var(--line); padding:8px 10px; border-radius:10px}
    .search-form input{background:transparent; border:none; outline:none; color:inherit; width:200px}

    .layout{display:grid; grid-template-columns: minmax(0,1fr) 320px; gap:26px; margin:24px 0}
    @media (max-width: 980px){ .layout{grid-template-columns: 1fr} }

    .content-area{display:grid; gap:18px}
    .post{background:var(--paper); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden}
    .post .post-inner{padding:16px}
    .post .entry-title{font: 700 22px/1.35 Georgia, "Times New Roman", Times, serif; margin:6px 0 8px}
    .post .entry-meta{display:flex; flex-wrap:wrap; gap:10px; color:var(--muted); font-size:13px}
    .entry-meta .sep{margin:0 4px; color:var(--line)}
    .post-thumbnail{aspect-ratio: 16/9; background:linear-gradient(135deg, #dde7f3, #f7fafc); border-bottom:1px solid var(--line)}
    @media (prefers-color-scheme: dark){ .post-thumbnail{ background:linear-gradient(135deg, #0f2236, #0b0f12) } }

    figure.wp-block-audio{margin:10px 0 0}
    audio{ width:100%; height:36px }

    .more-link, .button{display:inline-flex; align-items:center; gap:6px; padding:8px 12px; border-radius:10px; border:1px solid var(--line); background:var(--paper)}
    .button.primary{background:var(--primary); color:#fff; border-color:transparent}
    .button.primary:hover{background:var(--primary-600)}

    .tags-links a, .cat-links a{display:inline-block; padding:2px 8px; margin:0 6px 6px 0; border:1px solid var(--line); border-radius:999px; color:var(--muted)}
    .tags-links a:hover, .cat-links a:hover{border-color:var(--primary); color:var(--primary)}

    /* 侧边栏 */
    .widget-area{display:grid; gap:18px}
    .widget{background:var(--paper); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow)}
    .widget .widget-inner{padding:14px}
    .widget-title{margin:0 0 8px; font:700 16px/1.4 Georgia, "Times New Roman", Times, serif}
    .tag-cloud a{margin:6px 8px 0 0; display:inline-block}

    /* 分页 */
    .pagination{display:flex; justify-content:space-between; align-items:center; gap:10px; margin:4px 0 24px}
    .nav-links a{padding:8px 12px; border:1px solid var(--line); border-radius:10px; background:var(--paper)}
    .nav-links a[aria-disabled="true"]{opacity:.5; pointer-events:none}

    .site-footer{border-top:1px solid var(--line); color:var(--muted); padding:16px 0 40px}

    .screen-reader-text{position:absolute!important; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0}
  </style>
</head>
<body>
  <div class="site">
    <header class="site-header" role="banner">
      <div class="header-inner">
        <a class="site-branding" href="#" rel="home">
          <span class="site-logo">▶</span>
          <div>
            <div class="site-title">DevCast</div>
            <div class="site-description">WordPress 风格 · 播客</div>
          </div>
        </a>
        <nav class="primary-menu menu" aria-label="主菜单">
          <a href="#" aria-current="page">首页</a>
          <a href="#episodes">全部节目</a>
          <a href="#about">关于</a>
          <a href="feed.xml" title="RSS">订阅</a>
        </nav>
        <form class="search-form" role="search" onsubmit="event.preventDefault()">
          <label class="screen-reader-text" for="s">搜索</label>
          <svg width="16" height="16" aria-hidden="true" viewBox="0 0 24 24"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
          <input id="s" type="search" placeholder="搜索标题 / 摘要 / 标签" autocomplete="off" />
        </form>
      </div>
    </header>

    <main id="content" class="layout" role="main">
      <section class="content-area">
        <div id="loop" class="posts"></div>
        <nav class="navigation pagination" role="navigation" aria-label="文章导航">
          <h2 class="screen-reader-text">文章导航</h2>
          <div class="nav-links">
            <a id="prevPage" class="prev page-numbers" href="#">较新文章</a>
            <span id="pageIndicator" style="color:var(--muted)"></span>
            <a id="nextPage" class="next page-numbers" href="#">更早文章</a>
          </div>
        </nav>
      </section>

      <aside class="widget-area" role="complementary">
        <section class="widget">
          <div class="widget-inner">
            <h2 class="widget-title">订阅</h2>
            <p>通过 RSS 订阅最新节目：</p>
            <a class="button primary" href="feed.xml">RSS Feed</a>
          </div>
        </section>
        <section class="widget">
          <div class="widget-inner">
            <h2 class="widget-title">分类</h2>
            <div id="cats" class="cat-links"></div>
          </div>
        </section>
        <section class="widget">
          <div class="widget-inner">
            <h2 class="widget-title">归档</h2>
            <ul id="archives" style="padding-left:18px; margin:0"></ul>
          </div>
        </section>
        <section class="widget">
          <div class="widget-inner">
            <h2 class="widget-title">标签云</h2>
            <p id="tagCloud" class="tag-cloud"></p>
          </div>
        </section>
      </aside>
    </main>

    <footer class="site-footer">
      <div class="site">
        <small>© <span id="year"></span> DevCast · 外观致敬 WordPress · 部署于 GitHub Pages</small>
      </div>
    </footer>
  </div>

  <!-- 数据：你可以替换为自己的节目清单（也可外链 JSON 再用 fetch 加载） -->
  <script id="data" type="application/json">{
    "site": {
      "title": "DevCast",
      "description": "WordPress 风格播客",
      "author": "Your Name",
      "links": { "github": "https://github.com/yourname" }
    },
    "settings": { "perPage": 5 },
    "episodes": [
      {
        "id": "ep-003",
        "title": "边界与成本：何时该“写个小服务”？",
        "date": "2025-05-18",
        "duration": "36:42",
        "audio": "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3",
        "summary": "工程实践中的复用、封装与 SLA 思维。",
        "tags": ["architecture", "practice"],
        "category": "Architecture",
        "cover": ""
      },
      {
        "id": "ep-002",
        "title": "从脚手架到 DX：开发体验的四个层次",
        "date": "2025-02-02",
        "duration": "28:15",
        "audio": "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
        "summary": "聊聊 CLI、模板、DevServer、热更新与错误反馈。",
        "tags": ["dx", "tooling"],
        "category": "DX",
        "cover": ""
      },
      {
        "id": "ep-001",
        "title": "Hello, World：为什么是另一个播客？",
        "date": "2025-01-10",
        "duration": "12:34",
        "audio": "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
        "summary": "开场白：定位、选题和我们想传递的价值。",
        "tags": ["intro", "meta"],
        "category": "Meta",
        "cover": ""
      }
    ]
  }</script>

  <!-- 结构化数据（可选）：PodcastSeries 基本信息 -->
  <script type="application/ld+json" id="ld-series"></script>

  <script>
    // 工具
    const $ = (sel, el=document) => el.querySelector(sel);
    const $$ = (sel, el=document) => Array.from(el.querySelectorAll(sel));
    const fmtDate = (d) => new Date(d + 'T00:00:00').toLocaleDateString(undefined, {year:'numeric', month:'short', day:'numeric'});

    // 数据
    const data = JSON.parse(document.getElementById('data').textContent);
    const perPage = data.settings?.perPage ?? 5;
    document.title = `${data.site.title} — WordPress风格播客`;
    $('.site-title').textContent = data.site.title;
    $('.site-description').textContent = data.site.description;

    // 渲染器
    function placeholderSVG(title='Episode'){
      return `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 675' preserveAspectRatio='xMidYMid slice'>
        <defs>
          <linearGradient id='g' x1='0' y1='0' x2='1' y2='1'>
            <stop offset='0' stop-color='#dde7f3'/>
            <stop offset='1' stop-color='#f7fafc'/>
          </linearGradient>
        </defs>
        <rect width='1200' height='675' fill='url(#g)'/>
        <g font-family='system-ui, -apple-system, Segoe UI, Roboto, Helvetica' fill='#135e96'>
          <text x='48' y='120' font-size='54' font-weight='700'>${title}</text>
          <text x='48' y='180' font-size='28' opacity='.7'>DevCast • Podcast</text>
        </g>
      </svg>`;
    }

    function episodeHTML(ep){
      const cover = ep.cover ? `<img class="post-thumbnail" src="${ep.cover}" alt="${ep.title}">` : `<div class="post-thumbnail">${placeholderSVG(ep.title)}</div>`;
      const tags = (ep.tags||[]).map(t=>`<a href="#" data-tag="${t}">#${t}</a>`).join('');
      const cats = ep.category ? `<a href="#" data-cat="${ep.category}">${ep.category}</a>` : '';

      return `
      <article id="${ep.id}" class="post type-podcast hentry" itemscope itemtype="https://schema.org/PodcastEpisode">
        ${cover}
        <div class="post-inner">
          <header class="entry-header">
            <h2 class="entry-title" itemprop="name"><a href="#${ep.id}" rel="bookmark">${ep.title}</a></h2>
            <div class="entry-meta">
              <span class="posted-on">🗓️ <time datetime="${ep.date}" itemprop="datePublished">${fmtDate(ep.date)}</time></span>
              <span class="sep">•</span>
              <span class="byline">⏱️ ${ep.duration}</span>
              ${ep.category ? `<span class="sep">•</span><span class="cat-links">分类：${cats}</span>`:''}
            </div>
          </header>
          <div class="entry-summary" itemprop="description">${ep.summary||''}</div>
          <figure class="wp-block-audio"><audio preload="none" controls src="${ep.audio}" itemprop="associatedMedia" data-id="${ep.id}"></audio></figure>
          <p class="tags-links">${tags}</p>
          <p><a class="more-link" href="#${ep.id}" data-scroll>继续阅读 <span class="screen-reader-text">${ep.title}</span> →</a> <a class="button" href="${ep.audio}" download>下载音频</a></p>
          <meta itemprop="url" content="${location.href.split('#')[0]}#${ep.id}" />
          <meta itemprop="timeRequired" content="PT${(ep.duration||'0:00').replace(':','M')}S" />
        </div>
      </article>`;
    }

    // 列表、分页与筛选
    const loopEl = document.getElementById('loop');
    const pageIndicator = document.getElementById('pageIndicator');
    const prevBtn = document.getElementById('prevPage');
    const nextBtn = document.getElementById('nextPage');
    const searchInput = document.getElementById('s');

    let filtered = [...data.episodes].sort((a,b)=> new Date(b.date) - new Date(a.date));

    function renderPage(page=1){
      const total = filtered.length;
      const pages = Math.max(1, Math.ceil(total / perPage));
      page = Math.min(Math.max(1, page), pages);
      const start = (page-1)*perPage;
      const items = filtered.slice(start, start+perPage);
      loopEl.innerHTML = items.map(episodeHTML).join('');
      pageIndicator.textContent = `${page} / ${pages}`;
      prevBtn.setAttribute('aria-disabled', page===1);
      nextBtn.setAttribute('aria-disabled', page===pages);
      prevBtn.onclick = (e)=>{ e.preventDefault(); if(page>1){ setPage(page-1) } };
      nextBtn.onclick = (e)=>{ e.preventDefault(); if(page<pages){ setPage(page+1) } };
      // 事件：标签/分类筛选、保存进度
      $$('#loop audio').forEach(wireAudioProgress);
      $$('#loop .tags-links a').forEach(a=> a.addEventListener('click', (e)=>{ e.preventDefault(); filterByTag(a.dataset.tag) }));
      $$('#loop .cat-links a').forEach(a=> a.addEventListener('click', (e)=>{ e.preventDefault(); filterByCat(a.dataset.cat) }));
      $$('#loop a[data-scroll]').forEach(a=> a.addEventListener('click', (e)=>{ e.preventDefault(); document.getElementById(a.getAttribute('href').slice(1))?.scrollIntoView({behavior:'smooth', block:'center'}) }))
    }

    function setPage(page){
      const url = new URL(location.href);
      url.searchParams.set('page', page);
      history.replaceState(null, '', url);
      renderPage(page);
    }

    // 搜索
    function applySearch(){
      const q = searchInput.value.trim().toLowerCase();
      filtered = data.episodes.filter(ep=>{
        const hay = [ep.title, ep.summary, ep.category, ...(ep.tags||[])].join(' ').toLowerCase();
        return hay.includes(q);
      }).sort((a,b)=> new Date(b.date) - new Date(a.date));
      setPage(1);
      rebuildSidebars();
    }
    searchInput.addEventListener('input', applySearch);

    function filterByTag(tag){ searchInput.value = tag; applySearch(); }
    function filterByCat(cat){ searchInput.value = cat; applySearch(); }

    // 侧边栏：分类、归档、标签云
    function rebuildSidebars(){
      const cats = new Map();
      const tags = new Map();
      const archives = new Map(); // YYYY-MM
      filtered.forEach(ep=>{
        if(ep.category){ cats.set(ep.category, (cats.get(ep.category)||0)+1); }
        (ep.tags||[]).forEach(t=> tags.set(t, (tags.get(t)||0)+1));
        const d = new Date(ep.date+ 'T00:00:00');
        const key = d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0');
        archives.set(key, (archives.get(key)||0)+1);
      });
      // 分类
      const catsEl = document.getElementById('cats');
      catsEl.innerHTML = [...cats.entries()].sort().map(([c,n])=>`<a href="#" data-cat="${c}">${c} (${n})</a>`).join('');
      $$('#cats a').forEach(a=> a.addEventListener('click', (e)=>{ e.preventDefault(); filterByCat(a.dataset.cat) }));
      // 归档
      const archEl = document.getElementById('archives');
      archEl.innerHTML = [...archives.entries()].sort((a,b)=> a[0]<b[0]?1:-1).map(([k,n])=>{
        const [y,m] = k.split('-');
        return `<li><a href="#" data-ym="${k}">${y}年${m}月 (${n})</a></li>`;
      }).join('');
      $$('#archives a').forEach(a=> a.addEventListener('click', (e)=>{ e.preventDefault(); const ym=a.dataset.ym; searchInput.value = ym; applySearch(); }));
      // 标签云（按频次缩放）
      const max = Math.max(1, ...tags.values());
      const cloud = [...tags.entries()].sort((a,b)=> b[1]-a[1]).slice(0,24).map(([t,n])=>{
        const ratio = 0.8 + 1.2*(n/max); // 0.8x ~ 2.0x
        return `<a href="#" data-tag="${t}" style="font-size:${(ratio*14).toFixed(0)}px">#${t}</a>`;
      }).join(' ');
      document.getElementById('tagCloud').innerHTML = cloud || '<span style="color:var(--muted)">暂无标签</span>';
      $$('#tagCloud a').forEach(a=> a.addEventListener('click', (e)=>{ e.preventDefault(); filterByTag(a.dataset.tag) }));
    }

    // 播放进度保存（每个音频单例播放）
    function wireAudioProgress(audio){
      const key = 'progress:'+ (audio.dataset.id||audio.src);
      try{ const t = +localStorage.getItem(key); if(t>0) audio.currentTime = t; }catch{}
      audio.addEventListener('timeupdate', ()=>{ if(!audio.paused && !audio.seeking){ try{ localStorage.setItem(key, audio.currentTime) }catch{} } });
      audio.addEventListener('play', ()=>{ $$('#loop audio').forEach(a=>{ if(a!==audio) a.pause(); }) });
    }

    // JSON-LD（Series）
    (function renderLD(){
      const ld = {
        '@context':'https://schema.org', '@type':'PodcastSeries',
        'name': data.site.title,
        'description': data.site.description,
        'url': location.href.split('#')[0],
        'creator': data.site.author
      };
      document.getElementById('ld-series').textContent = JSON.stringify(ld);
    })();

    // 初始化
    (function init(){
      document.getElementById('year').textContent = new Date().getFullYear();
      const url = new URL(location.href);
      const p = +(url.searchParams.get('page')||1);
      rebuildSidebars();
      renderPage(p);
      // 如果 hash 指定某一篇，滚到它
      const hash = location.hash.slice(1); if(hash){ document.getElementById(hash)?.scrollIntoView({behavior:'smooth', block:'center'}) }
    })();
  </script>
</body>
</html>